/****************************************************************************
** Copyright (C) 2017 ZhouLiQiu(zhouliqiu@126.com).
**
** This file is part of the qtc_astyle, It is a plugin for Qt Creator.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
**
****************************************************************************/
#include "cartisticstyle.h"
#include "astyleactions.h"

#include <diffeditor/differ.h>
#include <texteditor/textdocumentlayout.h>

#include <QTextBlock>
#include <QScrollBar>
#include <QDebug>
#include <QPlainTextEdit>

namespace AStyle {
namespace Internal {

void STDCALL errorHandler(int number, const char *message)
{
    qDebug() << "Format error:" << number << message;
}

char *STDCALL memoryAlloc(unsigned long need)
{
    char *buffer = new(nothrow) char [need];
    return buffer;
}

CArtisticStyle::CArtisticStyle()
{
}

bool CArtisticStyle::formatString(const QString &in, QString &out)
{
    QString strArg = theAStyleSettings()->getAsArguments();
    char *textOut = AStyleMain(in.toUtf8().constData(),
                               strArg.toUtf8().constData(),
                               &AStyle::Internal::errorHandler,
                               &AStyle::Internal::memoryAlloc);
    if (textOut == NULL)
    {
        qDebug() << "Format fail";
        return false;
    }

    out = QString::fromUtf8(textOut);

    delete [] textOut;
    return true;
}

bool CArtisticStyle::formatEditor(QPlainTextEdit *textEditor)
{
    if (textEditor == NULL)
    {
        return false;
    }

    const QString sourceData = textEditor->toPlainText();
    QString formattedData;
    bool ret;

    ret = formatString(sourceData, formattedData);

    if ((!ret) || (sourceData == formattedData))
    {
        return false;
    }

    // Save folded blocks
    QList<int> collapsedBlocks;
    QTextBlock block = textEditor->document()->firstBlock();
    while (block.isValid())
    {
        if (!block.userData())
        {
            block = block.next();
            continue;
        }
        TextEditor::TextBlockUserData *userdata = static_cast<TextEditor::TextBlockUserData *>(block.userData());
        if (userdata->folded())
        {
            collapsedBlocks << block.blockNumber();
            TextEditor::TextDocumentLayout::doFoldOrUnfold(block, true);
        }
        block = block.next();
    }
    textEditor->update();

    // restore cursor
    QString tmp = sourceData.left(textEditor->textCursor().position());
    tmp.remove(QLatin1String(" "));
    tmp.remove(QLatin1String("\t"));
    tmp.remove(QLatin1String("\n"));
    int cursorPos = tmp.size();
    int newCursorPos = -1;
    int indexSourceX = cursorPos;
    while (0 > newCursorPos)
    {
        QString tmp = formattedData.left(indexSourceX);
        tmp.remove(QLatin1String(" "));
        tmp.remove(QLatin1String("\t"));
        tmp.remove(QLatin1String("\n"));
        if (tmp.size() == cursorPos)
        {
            newCursorPos = indexSourceX;
        }
        else
        {
            indexSourceX += cursorPos - tmp.size();
        }
    }
    QFontMetrics fm(textEditor->document()->defaultFont());
    int offset = textEditor->cursorRect().y() / fm.height();
    QTextCursor cursor = textEditor->textCursor();
    cursor.beginEditBlock();

    // diff
    DiffEditor::Differ differ;
    QList<DiffEditor::Diff> diff = differ.diff(sourceData, formattedData);

    cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
    int size = diff.size();
    for (int i = 0; i < size; ++i)
    {
        const DiffEditor::Diff d = diff.at(i);
        switch (d.command)
        {
        case DiffEditor::Diff::Insert:
            {
                if (d.text.contains(QChar(QLatin1Char('\n'))))
                {
                    int newLineCount = d.text.count(QChar(QLatin1Char('\n')));
                    int number = cursor.blockNumber();
                    int size = collapsedBlocks.size();
                    for (int i = 0; i < size; ++i)
                        if (collapsedBlocks.at(i) > number)
                        {
                            collapsedBlocks[i] += newLineCount;
                        }
                }
                cursor.insertText(d.text);
            }
            break;
        case DiffEditor::Diff::Delete:
            {
                if (d.text.contains(QChar(QLatin1Char('\n'))))
                {
                    int newLineCount = d.text.count(QChar(QLatin1Char('\n')));
                    int number = cursor.blockNumber();
                    int size = collapsedBlocks.size();
                    ///TODO Check if that calculation is right!
                    for (int i = 0; i < size; ++i)
                        if (collapsedBlocks.at(i) > number)
                        {
                            collapsedBlocks[i] -= newLineCount;
                            if (collapsedBlocks[i] < number)
                            {
                                collapsedBlocks.removeAt(i);
                                --i;
                                --size;
                            }
                        }
                }
                cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, d.text.size());
                cursor.removeSelectedText();
            }
            break;
        case DiffEditor::Diff::Equal:
            cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, d.text.size());
            break;
        }
    }

    cursor.endEditBlock();
    cursor.setPosition(newCursorPos);
    textEditor->setTextCursor(cursor);

    // restore folded blocks
    offset = textEditor->cursorRect().y() / fm.height() - offset;
    textEditor->verticalScrollBar()->setValue(textEditor->verticalScrollBar()->value() + offset);
    QTextDocument *doc = textEditor->document();
    size = collapsedBlocks.size();
    for (int i = 0; i < size; ++i)
    {
        QTextBlock block = doc->findBlockByNumber(qMax(0, collapsedBlocks.at(i)));
        if (block.isValid())
        {
            TextEditor::TextDocumentLayout::doFoldOrUnfold(block, false);
        }
    }

    textEditor->document()->setModified(true);
    return true;
}

} // namespace Internal
} // namespace AStyle


